/**
 * Copyright (c) 2009 Mujtaba Hassanpur.
 * 
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

import java.util.*;

/**
 * A collection of bricks. Allows access to the bricks and provides methods to
 * add and remove bricks from the bucket.
 */
public class Bucket extends Observable implements BrickCollection
{
	private static Bucket theBucket;
	
	public static final int BUCKET_WIDTH = 10;
	public static final int BUCKET_HEIGHT = 15;
	
	private Vector<Brick> worldObjects;
	private PuzzlePiece activePiece;
	//private int tickInterval; //in ms
	private int timeSinceLastInterval;
	
	/**
	 * Returns the current instance of the bucket.
	 * 
	 * @return Returns the current instance of the bucket.
	 */
	public static synchronized Bucket getBucket()
	{
		if(theBucket == null)
		{
			theBucket = new Bucket();
		}
		
		return theBucket;
	}
	
	/**
	 * Removes all objects from the bucket.
	 */
	public void removeAll()
	{
		worldObjects.removeAllElements();
	}
	
	/**
	 * Creates a collection of bricks.
	 */
	private Bucket()
	{
		worldObjects = new Vector<Brick>();
		activePiece = null;
		timeSinceLastInterval = 0;
	}
	
	public void setActivePuzzlePiece(PuzzlePiece piece)
	{
		activePiece = piece;
		activePiece.setPositionX((BUCKET_WIDTH/2) - (piece.getBoundaryRight() - 2));
		activePiece.setPositionY(BUCKET_HEIGHT - 1);
		notifyChanges();
	}
	
	public PuzzlePiece getActivePuzzlePiece()
	{
		return activePiece;
	}
	
	public void dumpActivePuzzlePieceToBucket()
	{		
		boolean isGameOver = false;
		Iterator i = activePiece.getIterator();
		while(i.hasNext())
		{
			worldObjects.add(((Brick)i.next()));
		}
		
		activePiece = null;
		
		// get the collision matrix
		Brick[][] matrix = getCollisionMatrix();
		
		// check if game is over?
		for(int j = 0; j < matrix[matrix.length - 1].length; j++)
		{
			if(matrix[matrix.length - 1][j] != null)
			{
				// a brick in the top most row means game over
				System.out.println("GAME OVER!");
				isGameOver = true;
				break;
			}
		}
		
		if(isGameOver)
		{
			CollapsingBricksGame.getCollapsingBricksGame().setGameState(CollapsingBricksGame.GameState.GAME_OVER);
		}
		else // game is not over
		{
			// sweep rows; go from top to bottom
			Vector<Brick> bricksAboveRow = new Vector<Brick>();
			for(int j = matrix.length - 1; j >= 0; j--)
			{
				boolean breakInRow = false;
				for(int k = 0; k < matrix[j].length; k++)
				{
					if(matrix[j][k] == null)
					{
						breakInRow = true;
					}
					else
					{
						bricksAboveRow.add(matrix[j][k]);
					}
				}
				
				// no breaks in this row, remove it
				if(!breakInRow)
				{
					// remove everything in this row
					for(int k = 0; k < matrix[j].length; k++)
					{
						worldObjects.removeElement(matrix[j][k]);
						bricksAboveRow.removeElement(matrix[j][k]);
						notifyChanges();
					}
					
					// update score
					Scoreboard.getScoreboard().incrementSweeps();
					
					// move everything above this row down
					Iterator aboveRow = bricksAboveRow.iterator();
					while(aboveRow.hasNext())
					{
						Brick current = (Brick)aboveRow.next();
						current.setPositionY(current.getPositionY() - 1);
					}
					
					notifyChanges();
					matrix = getCollisionMatrix();
				}
			}
			
			// create the next piece
			setActivePuzzlePiece(PuzzlePiece.createRandomPuzzlePiece());
		}
	}
	
	/**
	 * Adds a brick to the bucket.
	 */
	public void add(Brick element)
	{
		worldObjects.add(element);
		notifyChanges();
	}
	
	/**
	 * Removes a brick from bucket.
	 */
	public void remove(Brick element)
	{
		worldObjects.remove(element);
		notifyChanges();
	}
	
	/**
	 * Returns an Iterator to access all of the bricks.
	 */
	public Iterator getIterator()
	{
		return new GameIterator();
	}
	
	/**
	 * Sets changes to the Bucket state and notifies observers.
	 */
	public void notifyChanges()
	{
		setChanged();
		notifyObservers();
	}
	
	/**
	 * Allows iteration through the bricks.
	 */
	public class GameIterator implements Iterator
	{
		private Vector<Brick> currentVector;
		private Iterator thisIterator;
		
		/**
		 * Sets up an iterator that includes all of the bricks.
		 */
		private GameIterator()
		{
			currentVector = new Vector<Brick>();
			currentVector.addAll(worldObjects);
			if(activePiece != null)
			{
				Iterator i = activePiece.getIterator();
				while(i.hasNext())
				{
					currentVector.add((Brick)i.next());
				}
			}
			thisIterator = currentVector.iterator();
		}
		
		public boolean hasNext()
		{
			return thisIterator.hasNext();
		}
		
		public Brick next()
		{
			return (Brick)thisIterator.next();
		}
		
		public void remove()
		{
			throw new UnsupportedOperationException();
		}
	}
	
	/**
	 * Returns a two dimensional array that represents the board, with each
	 * brick located at [y][x], where x and y are the coordinates on the board.
	 * 
	 * @return The collision matrix.
	 */
	public Brick[][] getCollisionMatrix()
	{
		Brick[][] result = new Brick[BUCKET_HEIGHT][BUCKET_WIDTH];
		Iterator i = worldObjects.iterator();
		while(i.hasNext())
		{
			Brick temp = (Brick)(i.next());
			int x = temp.getPositionX();
			int y = temp.getPositionY();
			
			if(x >= 0 && x < BUCKET_WIDTH && y >= 0 && y < BUCKET_HEIGHT)
				result[y][x] = temp;
		}
		
		return result;
	}
	
	public void updateMovables(int elapsedTime)
	{
		timeSinceLastInterval += elapsedTime;
		if(activePiece != null && activePiece.getState() == PuzzlePiece.PieceState.NORMAL_FALL)
		{
			int speedModifier = Scoreboard.getScoreboard().getSpeedModifier();
			if(timeSinceLastInterval >= speedModifier)
			{
				timeSinceLastInterval -= speedModifier;
				activePiece.moveDown();
			}
		}
		else if(activePiece != null && activePiece.getState() == PuzzlePiece.PieceState.ACCELERATED_FALL)
		{
			activePiece.moveDown();
		}
		
		notifyChanges();
	}
}
